<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>视频合成服务</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }
        .container {
            max-width: 900px;
            margin: 0 auto;
            background: white;
            border-radius: 20px;
            box-shadow: 0 20px 40px rgba(0,0,0,0.1);
            overflow: hidden;
        }
        .header {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            padding: 30px;
            text-align: center;
        }
        .header h1 {
            font-size: 2.2em;
            margin-bottom: 10px;
        }
        .header p {
            font-size: 1.1em;
            opacity: 0.9;
        }
        .content {
            padding: 40px;
        }
        .form-section {
            margin-bottom: 30px;
        }
        .form-section h3 {
            color: #333;
            margin-bottom: 15px;
            font-size: 1.3em;
            border-bottom: 2px solid #667eea;
            padding-bottom: 10px;
        }
        .form-group {
            margin-bottom: 20px;
        }
        .form-group label {
            display: block;
            margin-bottom: 8px;
            font-weight: 600;
            color: #555;
        }
        .form-control {
            width: 100%;
            padding: 12px 15px;
            border: 2px solid #e1e5e9;
            border-radius: 10px;
            font-size: 16px;
            transition: all 0.3s ease;
        }
        .form-control:focus {
            outline: none;
            border-color: #667eea;
            box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
        }
        .form-row {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 20px;
        }
        .range-group {
            display: flex;
            align-items: center;
            gap: 15px;
        }
        .range-value {
            min-width: 50px;
            text-align: center;
            font-weight: 600;
            color: #667eea;
        }
        .btn {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border: none;
            padding: 15px 30px;
            border-radius: 10px;
            font-size: 16px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
            margin-right: 15px;
        }
        .btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
        }
        .btn:disabled {
            opacity: 0.6;
            cursor: not-allowed;
            transform: none;
        }
        .status {
            padding: 15px;
            border-radius: 10px;
            margin: 20px 0;
            font-weight: 600;
        }
        .status.success {
            background: #d4edda;
            color: #155724;
            border: 1px solid #c3e6cb;
        }
        .status.error {
            background: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
        }
        .video-section {
            margin-top: 30px;
            padding: 30px;
            background: #f8f9fa;
            border-radius: 15px;
            text-align: center;
        }
        .video-player {
            width: 100%;
            max-width: 600px;
            margin: 20px auto;
        }
        @media (max-width: 768px) {
            .form-row { grid-template-columns: 1fr; }
            .content { padding: 20px; }
            .header h1 { font-size: 2em; }
        }
    </style>
</head>
<body>
<div class="container">
    <div class="header">
        <h1>🎬 视频合成服务</h1>
        <p>输入图片和音频链接，设置参数，生成专属视频</p>
    </div>
    <div class="content">
        <form id="videoForm">
            <div class="form-section">
                <h3>基础信息</h3>
                <div class="form-group">
                    <label for="imageUrl">图片链接 *</label>
                </div>
                <div class="form-group" style="display: flex; align-items: center; gap: 10px;">
                    <input type="url" id="imageUrl" name="imageUrl" class="form-control" placeholder="请输入图片URL" required style="flex:1;">
                    <button type="button" class="btn" id="selectImageBtn" style="padding: 10px 18px; font-size: 15px; background: #007bff; color: #fff;">选择图片</button>
                    <button type="button" class="btn" id="uploadImageBtn" style="padding: 10px 18px; font-size: 15px; background: #28a745;">上传图片</button>
                    <button type="button" class="btn" id="checkImageBtn" style="padding: 10px 18px; font-size: 15px;">检测图片</button>
                    <span id="imageCheckResult" style="margin-left:8px;font-weight:600;color:#28a745;display:none;">✔ 检测通过</span>
                    <input type="file" id="imageFileInput" accept="image/*" style="display:none;">
                </div>
                <div id="imagePreview" style="margin-top:10px;"></div>
                <div class="progress-bar" id="imageProgressBar" style="display:none;margin-top:8px;height:8px;background:#e1e5e9;border-radius:4px;overflow:hidden;">
                    <div class="progress-fill" id="imageProgressFill" style="height:100%;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);width:0%;transition:width 0.3s;"></div>
                </div>
                <div id="uploadedImageUrl" style="margin-top:8px;word-break:break-all;color:#007bff;font-size:15px;"></div>
                <div class="form-group">
                    <label for="audioUrl">音频链接 *</label>
                    <div style="display: flex; align-items: center; gap: 10px;">
                        <input type="url" id="audioUrl" name="audioUrl" class="form-control" placeholder="请输入音频URL" required style="flex:1;">
                        <a href="/tts" class="btn" style="padding: 10px 18px; font-size: 15px; background: #ff9800; color: #fff; text-decoration: none;">生成音频</a>
                    </div>
                    <div id="audioPreview" style="margin-top:10px;"></div>
                </div>
                <div class="form-group">
                    <label for="duration">视频时长（秒，1-300）*</label>
                    <input type="number" id="duration" name="duration" class="form-control" min="1" max="300" required>
                </div>
            </div>
            <div class="form-section">
                <h3>高级参数（可选）</h3>
                <div class="form-row">
                    <div class="form-group">
                        <label for="templateId">模板风格</label>
                        <select id="templateId" name="templateId" class="form-control">
                            <option value="normal">normal（默认）</option>
                            <option value="calm">calm</option>
                            <option value="active">active</option>
                        </select>
                    </div>
                    <div class="form-group">
                        <label for="videoFps">视频帧率（15-30）</label>
                        <input type="number" id="videoFps" name="videoFps" class="form-control" min="15" max="30" value="24">
                    </div>
                </div>
                <div class="form-row">
                    <div class="form-group">
                        <label for="eyeMoveFreq">眨眼频率（0-1）</label>
                        <div class="range-group">
                            <input type="range" id="eyeMoveFreq" name="eyeMoveFreq" class="form-control" min="0" max="1" step="0.01" value="0.5">
                            <span class="range-value" id="eyeMoveFreqValue">0.5</span>
                        </div>
                    </div>
                    <div class="form-group">
                        <label for="mouthMoveStrength">嘴部动作幅度（0-1.5）</label>
                        <div class="range-group">
                            <input type="range" id="mouthMoveStrength" name="mouthMoveStrength" class="form-control" min="0" max="1.5" step="0.01" value="1.0">
                            <span class="range-value" id="mouthMoveStrengthValue">1.0</span>
                        </div>
                    </div>
                </div>
                <div class="form-row">
                    <div class="form-group">
                        <label for="headMoveStrength">头部动作幅度（0-1）</label>
                        <div class="range-group">
                            <input type="range" id="headMoveStrength" name="headMoveStrength" class="form-control" min="0" max="1" step="0.01" value="0.7">
                            <span class="range-value" id="headMoveStrengthValue">0.7</span>
                        </div>
                    </div>
                    <div class="form-group">
                        <label for="volume">音量（20-100）</label>
                        <div class="range-group">
                            <input type="range" id="volume" name="volume" class="form-control" min="20" max="100" value="80">
                            <span class="range-value" id="volumeValue">80</span>
                        </div>
                    </div>
                </div>
                <div class="form-row">
                    <div class="form-group">
                        <label for="pasteBack">贴回原图</label>
                        <select id="pasteBack" name="pasteBack" class="form-control">
                            <option value="true">是</option>
                            <option value="false">否</option>
                        </select>
                    </div>
                    <div class="form-group">
                        <label for="remark">备注（最多1000字）</label>
                        <input type="text" id="remark" name="remark" class="form-control" maxlength="1000">
                    </div>
                </div>
            </div>
            <div class="form-section">
                <button type="submit" class="btn">🚀 生成视频</button>
                <button type="reset" class="btn btn-secondary">重置</button>
            </div>
        </form>
        <div id="status" class="status" style="display:none;"></div>
        <div id="videoSection" class="video-section" style="display:none;">
            <h3>🎥 生成结果</h3>
            <video id="videoPlayer" class="video-player" controls></video>
            <div style="margin-top: 20px;">
                <a id="videoLink" href="#" target="_blank">点击打开视频链接</a>
            </div>
        </div>
    </div>
</div>
<script>
// 提交表单
const form = document.getElementById('videoForm');
const statusDiv = document.getElementById('status');
const videoSection = document.getElementById('videoSection');
const videoPlayer = document.getElementById('videoPlayer');
const videoLink = document.getElementById('videoLink');

form.addEventListener('submit', async function(e) {
    e.preventDefault();
    statusDiv.style.display = 'block';
    statusDiv.className = 'status info';
    statusDiv.textContent = '正在提交生成请求，请稍候...';
    videoSection.style.display = 'none';

    // 构造请求体
    const data = {
        model: 'liveportrait',
        input: {
            image_url: form.imageUrl.value,
            audio_url: form.audioUrl.value
        },
        parameters: {
            template_id: form.templateId.value,
            eye_move_freq: parseFloat(form.eyeMoveFreq.value),
            video_fps: parseInt(form.videoFps.value),
            mouth_move_strength: parseFloat(form.mouthMoveStrength.value),
            paste_back: form.pasteBack.value === 'true',
            head_move_strength: parseFloat(form.headMoveStrength.value),
            duration: parseFloat(form.duration.value),
            volume: form.volume.value ? parseInt(form.volume.value) : undefined,
            remark: form.remark.value || undefined
        }
    };

    // 移除未填写的可选参数
    Object.keys(data.parameters).forEach(key => {
        if (data.parameters[key] === undefined || data.parameters[key] === "") {
            delete data.parameters[key];
        }
    });

    try {
        const resp = await fetch('/api/video/generate', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(data)
        });
        const result = await resp.json();
        if (result && result.output && result.output.task_id) {
            // 进入轮询
            statusDiv.className = 'status info';
            statusDiv.textContent = '任务已提交，正在生成视频，请耐心等待...';
            const taskId = result.output.task_id;
            let videoUrl = null;
            let attempts = 0;
            const maxAttempts = 120;
            const waitSeconds = 5;
            while (attempts < maxAttempts) {
                await new Promise(res => setTimeout(res, waitSeconds * 1000));
                attempts++;
                statusDiv.textContent = `正在生成视频（已等待${attempts * waitSeconds}秒）...`;
                let statusResp, statusResult;
                try {
                    statusResp = await fetch(`/api/video/status/${taskId}`);
                    statusResult = await statusResp.json();
                } catch (e) {
                    statusDiv.className = 'status error';
                    statusDiv.textContent = '轮询接口请求失败：' + e.message;
                    return;
                }
                // 兼容两种写法
                videoUrl = statusResult.generatedVideoUrl || (statusResult.output && statusResult.output.video_url);
                if (statusResp.ok && videoUrl) {
                    break;
                }
                if (statusResult.failed || (statusResult.output && statusResult.output.task_status === 'FAILED')) {
                    statusDiv.className = 'status error';
                    statusDiv.textContent = '生成失败：' + (statusResult.output && statusResult.output.message ? statusResult.output.message : '任务失败');
                    return;
                }
            }
            if (videoUrl) {
                statusDiv.className = 'status success';
                statusDiv.textContent = '视频生成成功！';
                videoSection.style.display = 'block';
                videoPlayer.src = videoUrl;
                videoLink.href = videoUrl;
                videoLink.textContent = videoUrl;
            } else {
                statusDiv.className = 'status error';
                statusDiv.textContent = '生成超时或失败，请稍后重试。';
            }
        } else {
            statusDiv.className = 'status error';
            statusDiv.textContent = '生成失败：' + (result.output && result.output.message ? result.output.message : '未知错误');
        }
    } catch (err) {
        statusDiv.className = 'status error';
        statusDiv.textContent = '请求失败：' + err.message;
    }
});

// 滑块显示
function bindRangeValue(rangeId, valueId, multiplier = 1) {
    const range = document.getElementById(rangeId);
    const value = document.getElementById(valueId);
    if (range && value) {
        range.addEventListener('input', function() {
            value.textContent = (this.value * multiplier).toFixed(2).replace(/\.00$/, '');
        });
    }
}
bindRangeValue('eyeMoveFreq', 'eyeMoveFreqValue');
bindRangeValue('mouthMoveStrength', 'mouthMoveStrengthValue');
bindRangeValue('headMoveStrength', 'headMoveStrengthValue');
bindRangeValue('volume', 'volumeValue', 1);

// 图片预览
const imageInput = document.getElementById('imageUrl');
const imagePreview = document.getElementById('imagePreview');
imageInput.addEventListener('input', function() {
    const url = this.value.trim();
    if (url && (url.startsWith('http://') || url.startsWith('https://'))) {
        imagePreview.innerHTML = `<img src="${url}" alt="图片预览" style="max-width:300px;max-height:180px;border-radius:8px;border:1px solid #eee;">`;
    } else {
        imagePreview.innerHTML = '';
    }
});

// 音频预览
const audioInput = document.getElementById('audioUrl');
const audioPreview = document.getElementById('audioPreview');
audioInput.addEventListener('input', function() {
    const url = this.value.trim();
    if (url && (url.endsWith('.mp3') || url.endsWith('.wav') || url.endsWith('.ogg') || url.endsWith('.aac'))) {
        audioPreview.innerHTML = `<audio src="${url}" controls style="width:300px;"></audio>`;
    } else {
        audioPreview.innerHTML = '';
    }
});

// 图片检测功能
const checkImageBtn = document.getElementById('checkImageBtn');
const imageCheckResult = document.getElementById('imageCheckResult');
checkImageBtn.addEventListener('click', async function() {
    imageCheckResult.style.display = 'none';
    const url = imageInput.value.trim();
    if (!url || !(url.startsWith('http://') || url.startsWith('https://'))) {
        alert('请输入有效的图片URL');
        return;
    }
    checkImageBtn.disabled = true;
    checkImageBtn.textContent = '检测中...';
    try {
        const resp = await fetch('/api/image/validate', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ imageUrl: url })
        });
        const result = await resp.json();
        if (result.pass) {
            imageCheckResult.style.display = 'inline';
            imageCheckResult.textContent = '✔ 检测通过';
            imageCheckResult.style.color = '#28a745';
        } else {
            alert('图片检测未通过：' + (result.message || '未知错误'));
        }
    } catch (e) {
        alert('图片检测失败：' + e.message);
    } finally {
        checkImageBtn.disabled = false;
        checkImageBtn.textContent = '检测图片';
    }
});

// 图片选择和上传功能
const selectImageBtn = document.getElementById('selectImageBtn');
const uploadImageBtn = document.getElementById('uploadImageBtn');
const imageFileInput = document.getElementById('imageFileInput');
const uploadedImageUrl = document.getElementById('uploadedImageUrl');
const imageProgressBar = document.getElementById('imageProgressBar');
const imageProgressFill = document.getElementById('imageProgressFill');
let selectedFile = null;

selectImageBtn.addEventListener('click', function() {
    imageFileInput.value = '';
    imageFileInput.click();
});

imageFileInput.addEventListener('change', function() {
    if (!this.files || this.files.length === 0) return;
    selectedFile = this.files[0];
    // 预览图片
    const reader = new FileReader();
    reader.onload = function(e) {
        imagePreview.innerHTML = `<img src="${e.target.result}" alt="图片预览" style="max-width:300px;max-height:180px;border-radius:8px;border:1px solid #eee;">`;
    };
    reader.readAsDataURL(selectedFile);
    uploadedImageUrl.textContent = '';
    imageProgressBar.style.display = 'none';
    imageProgressFill.style.width = '0%';
    imageInput.value = '';
});

uploadImageBtn.addEventListener('click', function() {
    if (!selectedFile) {
        alert('请先选择图片');
        return;
    }
    uploadImageBtn.disabled = true;
    uploadImageBtn.textContent = '上传中...';
    uploadedImageUrl.textContent = '';
    imageProgressBar.style.display = 'block';
    imageProgressFill.style.width = '0%';
    const formData = new FormData();
    formData.append('file', selectedFile);
    const xhr = new XMLHttpRequest();
    xhr.open('POST', '/api/upload/image');
    xhr.upload.addEventListener('progress', function(e) {
        if (e.lengthComputable) {
            const percent = (e.loaded / e.total) * 100;
            imageProgressFill.style.width = percent + '%';
        }
    });
    xhr.onload = function() {
        uploadImageBtn.disabled = false;
        uploadImageBtn.textContent = '上传图片';
        if (xhr.status === 200) {
            let resp;
            try { resp = JSON.parse(xhr.responseText); } catch { resp = {}; }
            if (resp.url) {
                uploadedImageUrl.textContent = resp.url;
                imageInput.value = resp.url;
                imageInput.dispatchEvent(new Event('input'));
            } else {
                alert('图片上传失败：' + (resp.message || '未知错误'));
            }
        } else {
            alert('图片上传失败：HTTP ' + xhr.status);
        }
    };
    xhr.onerror = function() {
        uploadImageBtn.disabled = false;
        uploadImageBtn.textContent = '上传图片';
        alert('图片上传异常');
    };
    xhr.send(formData);
});
</script>
</body>
</html> 